package com.dynamic.mybatis.core.enums;

import com.dynamic.mybatis.core.utils.StringUtils;

import java.math.BigDecimal;
import java.sql.JDBCType;
import java.sql.Types;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Date;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
    * @Description
    * @Author xs
    * @see java.sql.JDBCType
    * @see org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl
    * @Date 2023/3/15 18:27
    *
    * jdbc 映射java类 JavaTypeResolverDefaultImpl 相关架包
    * <dependency>
    *  <groupId>org.mybatis.generator</groupId>
    *  <artifactId>mybatis-generator-maven-plugin</artifactId>
    *  <version>1.4.2</version>
    * </dependency>
 */
public interface Type {
    Set<String> NUMBER_TYPES = Stream.of(StandardType.TINYINT.javaTypeAlias, StandardType.SMALLINT.javaTypeAlias,
            StandardType.INTEGER.javaTypeAlias, StandardType.BIGINT.javaTypeAlias
            , StandardType.FLOAT.javaTypeAlias, StandardType.DOUBLE.javaTypeAlias).collect(Collectors.toSet());

    Set<String> DATE_TIME_TYPES = Stream.of(StandardType.DATE.javaTypeAlias, StandardType.TIME.javaTypeAlias, StandardType.TIMESTAMP.javaTypeAlias).collect(Collectors.toSet());

    int getJdbcType();

    String getJavaTypeAlias();

    Class getJavaType();

    BigDecimal getMin();

    BigDecimal getMax();

    BigDecimal getLength();

    enum StandardType implements Type {
        /**
         * Identifies the generic SQL type {@code BIT}.
         */
        BIT(Types.BIT, "bit",Boolean.TYPE, BigDecimal.valueOf(0), BigDecimal.valueOf(1)),
        /**
         * Identifies the generic SQL type {@code TINYINT}.
         */
        TINYINT(Types.TINYINT, "byte",Byte.TYPE, BigDecimal.valueOf(Byte.MIN_VALUE), BigDecimal.valueOf(Byte.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code SMALLINT}.
         */
        SMALLINT(Types.SMALLINT, "short",Short.TYPE, BigDecimal.valueOf(Short.MIN_VALUE), BigDecimal.valueOf(Short.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code INTEGER}.
         */
        INTEGER(Types.INTEGER, "int",Integer.TYPE, BigDecimal.valueOf(Integer.MIN_VALUE), BigDecimal.valueOf(Integer.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code BIGINT}.
         */
        BIGINT(Types.BIGINT, "long",Long.TYPE, BigDecimal.valueOf(Long.MIN_VALUE), BigDecimal.valueOf(Long.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code FLOAT}.
         */
        FLOAT(Types.FLOAT, "float",Float.TYPE, BigDecimal.valueOf(Float.MIN_VALUE), BigDecimal.valueOf(Float.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code REAL}.
         */
        REAL(Types.REAL,"float",Float.TYPE, BigDecimal.valueOf(Float.MIN_VALUE), BigDecimal.valueOf(Float.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code DOUBLE}.
         */
        DOUBLE(Types.DOUBLE, "double",Double.TYPE, BigDecimal.valueOf(Double.MIN_VALUE), BigDecimal.valueOf(Double.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code NUMERIC}.
         */
        NUMERIC(Types.NUMERIC,"bigdecimal",BigDecimal.class),
        /**
         * Identifies the generic SQL type {@code DECIMAL}.
         */
        DECIMAL(Types.DECIMAL,"bigdecimal",BigDecimal.class),
        /**
         * Identifies the generic SQL type {@code CHAR}.
         */
        CHAR(Types.CHAR, "byte",String.class, BigDecimal.valueOf(Byte.MIN_VALUE), BigDecimal.valueOf(Byte.MAX_VALUE)),
        /**
         * Identifies the generic SQL type {@code VARCHAR}.
         */
        VARCHAR(Types.VARCHAR, "string",String.class),
        /**
         * Identifies the generic SQL type {@code LONGVARCHAR}.
         */
        LONGVARCHAR(Types.LONGVARCHAR, "string",String.class),
        /**
         * Identifies the generic SQL type {@code DATE}.
         */
        DATE(Types.DATE, "date", Date.class),
        /**
         * Identifies the generic SQL type {@code TIME}.
         */
        TIME(Types.TIME, "time", Date.class),
        /**
         * Identifies the generic SQL type {@code TIMESTAMP}.
         */
        TIMESTAMP(Types.TIMESTAMP, "timestamp", Date.class),
        /**
         * Identifies the generic SQL type {@code BINARY}.
         */
        BINARY(Types.BINARY,"byte[]",Byte[].class),
        /**
         * Identifies the generic SQL type {@code VARBINARY}.
         */
        VARBINARY(Types.VARBINARY,"byte[]",Byte[].class),
        /**
         * Identifies the generic SQL type {@code LONGVARBINARY}.
         */
        LONGVARBINARY(Types.LONGVARBINARY,"byte[]",Byte[].class),
        /**
         * Identifies the generic SQL value {@code NULL}.
         */
        NULL(Types.NULL,"null",Object.class),
        /**
         * Indicates that the SQL type
         * is database-specific and gets mapped to a Java object that can be
         * accessed via the methods getObject and setObject.
         */
        OTHER(Types.OTHER,"struct",Object.class),
        /**
         * Indicates that the SQL type
         * is database-specific and gets mapped to a Java object that can be
         * accessed via the methods getObject and setObject.
         */
        JAVA_OBJECT(Types.JAVA_OBJECT,"struct",Object.class),
        /**
         * Identifies the generic SQL type {@code DISTINCT}.
         */
        DISTINCT(Types.DISTINCT,"struct",Object.class),
        /**
         * Identifies the generic SQL type {@code STRUCT}.
         */
        STRUCT(Types.STRUCT, "struct",Object.class),
        /**
         * Identifies the generic SQL type {@code ARRAY}.
         */
        ARRAY(Types.ARRAY, "array",Object.class),
        /**
         * Identifies the generic SQL type {@code BLOB}.
         */
        BLOB(Types.BLOB,"byte[]",Byte[].class),
        /**
         * Identifies the generic SQL type {@code CLOB}.
         */
        CLOB(Types.CLOB,"string",String.class),
        /**
         * Identifies the generic SQL type {@code REF}.
         */
        REF(Types.REF,"object",Object.class),
        /**
         * Identifies the generic SQL type {@code DATALINK}.
         */
        DATALINK(Types.DATALINK,"object",Object.class),
        /**
         * Identifies the generic SQL type {@code BOOLEAN}.
         */
        BOOLEAN(Types.BOOLEAN, "boolean",Boolean.TYPE),

        /* JDBC 4.0 Types */

        /**
         * Identifies the SQL type {@code ROWID}.
         */
        ROWID(Types.ROWID),
        /**
         * Identifies the generic SQL type {@code NCHAR}.
         */
        NCHAR(Types.NCHAR,"string",String.class),
        /**
         * Identifies the generic SQL type {@code NVARCHAR}.
         */
        NVARCHAR(Types.NVARCHAR,"string",String.class),
        /**
         * Identifies the generic SQL type {@code LONGNVARCHAR}.
         */
        LONGNVARCHAR(Types.LONGNVARCHAR,"string",String.class),
        /**
         * Identifies the generic SQL type {@code NCLOB}.
         */
        NCLOB(Types.NCLOB,"string",String.class),
        /**
         * Identifies the generic SQL type {@code SQLXML}.
         */
        SQLXML(Types.SQLXML),

        /* JDBC 4.2 Types */

        /**
         * Identifies the generic SQL type {@code REF_CURSOR}.
         */
        REF_CURSOR(Types.REF_CURSOR),

        /**
         * Identifies the generic SQL type {@code TIME_WITH_TIMEZONE}.
         */
        TIME_WITH_TIMEZONE(Types.TIME_WITH_TIMEZONE,null, OffsetTime.class),

        /**
         * Identifies the generic SQL type {@code TIMESTAMP_WITH_TIMEZONE}.
         */
        TIMESTAMP_WITH_TIMEZONE(Types.TIMESTAMP_WITH_TIMEZONE,null, OffsetDateTime.class);;

        StandardType(int jdbcType) {
            this.jdbcType = jdbcType;
        }

        StandardType(int jdbcType, String javaTypeAlias) {
            this.jdbcType = jdbcType;
            this.javaTypeAlias = javaTypeAlias;
        }

        StandardType(int jdbcType, String javaTypeAlias, Class javaType) {
            this.jdbcType = jdbcType;
            this.javaTypeAlias = javaTypeAlias;
            this.javaType = javaType;
        }

        StandardType(int jdbcType, String javaTypeAlias,Class javaType, BigDecimal min, BigDecimal max) {
            this.jdbcType = jdbcType;
            this.javaTypeAlias = javaTypeAlias;
            this.javaType = javaType;
            this.min = min;
            this.max = max;
        }

        public int jdbcType;

        public String javaTypeAlias;
        public Class javaType;
        public BigDecimal min;
        public BigDecimal max;
        public BigDecimal length;

        @Override
        public String getJavaTypeAlias() {
            return javaTypeAlias;
        }

        @Override
        public int getJdbcType() {
            return jdbcType;
        }

        public void setJdbcType(int jdbcType) {
            this.jdbcType = jdbcType;
        }

        public void setJavaTypeAlias(String javaTypeAlias) {
            this.javaTypeAlias = javaTypeAlias;
        }

        public Class getJavaType() {
            return javaType;
        }

        public void setJavaType(Class javaType) {
            this.javaType = javaType;
        }

        @Override
        public BigDecimal getMin() {
            return min;
        }

        public void setMin(BigDecimal min) {
            this.min = min;
        }

        @Override
        public BigDecimal getMax() {
            return max;
        }

        public void setMax(BigDecimal max) {
            this.max = max;
        }

        @Override
        public BigDecimal getLength() {
            return length;
        }

        public void setLength(BigDecimal length) {
            this.length = length;
        }
    }


    static Type getType(JDBCType jdbcType) {
        return getTypeByJdbcType(jdbcType.getVendorTypeNumber());
    }

    static Type getTypeByJavaTypeAlias(String type) {
        if (StringUtils.isBlank(type)) {
            return null;
        }
        for (StandardType t : Type.StandardType.values()) {
            if (type.equalsIgnoreCase(t.javaTypeAlias)) {
                return t;
            }
        }
        return null;
    }

    static Type getTypeByJdbcType(int jdbcType) {
        for (StandardType t : Type.StandardType.values()) {
            if (t.jdbcType == jdbcType) {
                return t;
            }
        }
        return null;
    }
}